package nz.ac.massey.groupproject.ecs2;

import java.util.ArrayList;

import java.awt.image.BufferedImage;
import java.awt.Graphics2D;

import java.awt.Dimension;
import java.awt.Color;

// tmp: Always run in Swing's thread
class Renderer {
	static BufferedImage render (Dimension sz, ECSWorld world) {

		var img = new BufferedImage(sz.width, sz.height, BufferedImage.TYPE_INT_RGB); // TODO use native color format
		var g = new AWTDrawingContext(img);

		actuallyRender(g, sz, world);

		g.dispose(); // promptly release OS resources, don't wait for GC
		return img;
	}

	private static void actuallyRender(UnifiedDrawingContext g, Dimension sz, ECSWorld world) {
		var globalData = (GameState) world.globalData;

		// clear screen
		g.setColor(64, 64, 64); // AWT darkGray
		g.fillRect(0, 0, sz.width, sz.height);

		// world border
		g.setColor(0, 0, 0); // black
		g.drawRect(0, 0, globalData.XSPAN +2, globalData.YSPAN +2);

		// TODO sz
		// translate and scale (world coor to window coor)

		// entities
		for (var t : world.entities)  drawWall  (g, sz, globalData, t);
		for (var t : world.entities)  drawEntity(g, sz, globalData, t);
		for (var t : world.entities)  drawDebugOverlay(g, sz, globalData, t);
	}

	private static void drawEntity(UnifiedDrawingContext g, Dimension sz, GameState globalData, ECSEntity e) {
		var pos    = (Position)   e.getComponent(Position  .class);
		var appear = (Appearance) e.getComponent(Appearance.class);
		Vec2i screenxy = gameCoorToScreenCoor(globalData, pos.x, pos.y);
		int x = screenxy.x;
		int y = screenxy.y;
		// TODO skip if no appearance

		if (Appearance.TINY_DOT == appear) {
			g.setColor(192, 192, 192); // AWT lightGray
			fillRectByCenter(g, x, y, 2);

		} else if (Appearance.DEBUG_TINY_DOT1 == appear) {
			g.setColor(255, 64, 64);  // red-ish
			fillRectByCenter(g, x, y, 2);

		} else if (Appearance.DEBUG_TINY_DOT2 == appear) {
			g.setColor(64, 64, 255);  // blue-ish
			fillRectByCenter(g, x, y, 2);

		} else if (Appearance.SMALL_DOT == appear) {
			g.setColor(192, 192, 192); // AWT lightGray
			fillRectByCenter(g, x, y, 3);

		} else if (Appearance.INVISIBLE == appear) {
			// skip

		} else if (Appearance.PLAYER == appear) {
			g.setColor(0, 255, 255); // cyan
			fillRectByCenter(g, x, y, 6);

		} else {
			System.err.println("actuallyRender(): unknown Appearance");
			System.exit(3);
		}
	}

	// FIXME Currently we draw any entity with a polygon, whether it is meant to be shown or not
	// FIXME code duplication
	// TODO doc: PolygonComponent must be accompanied by Position
	private static void drawWall(UnifiedDrawingContext g, Dimension sz, GameState globalData, ECSEntity e) {
		if (! e.has(WallTag.class)) return;
		var pos    = (Position)         e.getComponent(Position        .class);
		var polyc  = (PolygonComponent) e.getComponent(PolygonComponent.class);

		g.setColor(192, 192, 192); // AWT lightGray
		fillPolygon(g, globalData, pos, polyc.poly);
	}

	private static void drawDebugOverlay(UnifiedDrawingContext g, Dimension sz, GameState globalData, ECSEntity e) {
		if (e.has(WallTag.class)) return;
		var pos    = (Position)         e.getComponent(Position        .class);
		var polyc  = (PolygonComponent) e.getComponent(PolygonComponent.class);
		if (polyc == null) return;

		g.setColor(255, 128, 128, 100); // light red
		fillPolygon(g, globalData, pos, polyc.poly);
	}





	public static Vec2i gameCoorToScreenCoor(GameState globalData, int gamex, int gamey) {
		int x = 1 + (gamex - globalData.MINX);
		int y = 1 + (globalData.MAXY - gamey);
		return new Vec2i( x, y );
	}




	// len = radius * 2 - 1
	public static void fillRectByCenter(UnifiedDrawingContext g, int cx, int cy, int radius) {
		if (radius == 0) return;
		if (radius < 0) radius = -radius;
		int l = radius * 2 - 1;
		g.fillRect(cx-radius+1, cy-radius+1, l, l);
	}

	public static void fillPolygon(UnifiedDrawingContext g, GameState globalData, Position pos, Polygon poly) {
		ArrayList<Vec2l> points = poly.vs;
		int n = points.size();

		int[] xs = new int[n];
		int[] ys = new int[n];
		for (int i = 0; i < n; i++) {
			int gamex = pos.x + (int) points.get(i).x;
			int gamey = pos.y + (int) points.get(i).y;
			var pixel = gameCoorToScreenCoor(globalData, gamex, gamey);
			xs[i] = pixel.x;
			ys[i] = pixel.y;
		}

		g.fillPolygon(xs, ys);
	}
}

// FIXME the following intertwines with AWT (i.e. shouldn't be part of Renderer)
class AWTDrawingContext implements UnifiedDrawingContext {
	private final Graphics2D g;

	public AWTDrawingContext(BufferedImage img) {  g = img.createGraphics();  }
	public void dispose() {  g.dispose();  }


	public void setColor(int r, int green, int b)        {  g.setColor(new Color(r, green, b   ));  }
	public void setColor(int r, int green, int b, int a) {  g.setColor(new Color(r, green, b, a));  }

	public void fillRect(int x1, int y1, int w, int h) {  var cr = canon(x1,y1,w,h);   g.fillRect(cr.left, cr.top, cr.w  , cr.h  );  }
	public void drawRect(int x1, int y1, int w, int h) {  var cr = canon(x1,y1,w,h);   g.drawRect(cr.left, cr.top, cr.w-1, cr.h-1);  }


	public void fillPolygon(int[] xs, int[] ys) {  g.fillPolygon(xs, ys, xs.length);  }   // XXX no sanity check



	// helpers  (for negative width and height)
	private static class Rectangle {
		public int left, top, w, h;
		public Rectangle(int l, int t, int w_, int h_) {  left = l;  top = t;  w = w_;  h = h_;  }
	}
	private static Rectangle canon(int x1, int y1, int w, int h) {
		if (w < 0) {  x1 += w;  w = -w;  };
		if (h < 0) {  y1 += h;  h = -h;  };
		return new Rectangle(x1, y1, w, h);
	}

	public static void testSpecialCases(UnifiedDrawingContext dc) {
		// checker grid for visual reference
		for (int r=0, y = 20; y < 1000; r+=1, y+=20) {
			for (int c = 0, x = 20; x < 1000; c+=1, x += 20) {
				if ((r+c) % 2 == 0) dc.setColor(162, 162, 192); else dc.setColor(162, 192, 192);
				dc.fillRect(x,y,20,20);
			}
		}

		// draw various cases for manual verification
		dc.setColor(0, 0, 0); // black
		dc.fillRect(100, 100, 1, 1); dc.fillRect(120, 100, 2, 2); dc.fillRect(140, 100, 3, 3); dc.fillRect(160, 100, 0, 0); dc.fillRect(180, 100, -1, -1); dc.fillRect(200, 100, -2, -2);
		dc.drawRect(100, 120, 1, 1); dc.drawRect(120, 120, 2, 2); dc.drawRect(140, 120, 3, 3); dc.drawRect(160, 120, 0, 0); dc.drawRect(180, 120, -1, -1); dc.drawRect(200, 120, -2, -2);

		dc.fillRect(220, 100, 3, -3); dc.fillRect(240, 100, 3, 3); dc.fillRect(260, 100, -3, 3); dc.fillRect(280, 100, -3, 0); dc.fillRect(300, 100, 0, -3); dc.fillRect(320, 100, 3, 3);
		dc.drawRect(220, 120, 3, -3); dc.fillRect(240, 120, 3, 3); dc.drawRect(260, 120, -3, 3); dc.drawRect(280, 120, -3, 0); dc.drawRect(300, 120, 0, -3); dc.fillRect(320, 120, 3, 3);
	}
}
